Skip to content

feat(fmt): rewrite formatter using Solar and a structured algorithm #10907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 123 commits into
base: master
Choose a base branch
from

Conversation

0xrusowsky
Copy link
Contributor

@0xrusowsky 0xrusowsky commented Jul 2, 2025

Motivation

Rewrite forge-fmt entirely to use Solar as the Solidity/Yul parser and AST, and using a structured pretty-printing algorithm.

The language-agnostic implementation of the pretty-printer (src/pp) is based on rustc_ast_pretty and prettyplease, both MIT and Apache licensed. See prettyplease's README for algorithm notes.

The goal of this PR is to make progress with #9317, and fixing existing issues with the formatter while trying to maintain compatibility as much as possible.

This PR will not be replacing the formatter yet, opting to create a separate crate crates/fmt-2 to reduce unrelated changes and aid with review. A follow-up PR will replace crates/fmt and deal with fallout in-tree.

Issues

Issues that will be fixed with the new implementation. Not closed by this PR, but by the follow-up that will hook the new implementation up; see paragraph above.

Tests state

  • ArrayExpressions
  • BlockComments
  • BlockCommentsFunction
  • ConditionalOperatorExpression
  • ConstructorDefinition
  • ConstructorModifierStyle
  • ContractDefinition
  • DocComments
  • DoWhileStatement
  • EmitStatement
  • EnumDefinition
  • EnumVariants
  • ErrorDefinition
  • EventDefinition
  • ForStatement
  • FunctionCall
  • FunctionCallArgsStatement
  • FunctionDefinition
  • FunctionDefinitionWithFunctionReturns
  • FunctionType
  • HexUnderscore
  • IfStatement
  • IfStatement2
  • ImportDirective
  • InlineDisable
  • IntTypes
  • LiteralExpression
  • MappingType
  • ModifierDefinition
  • NamedFunctionCallExpression
  • NumberLiteralUnderscore
  • OperatorExpressions
  • PragmaDirective
  • Repros
  • ReturnStatement
  • RevertNamedArgsStatement
  • RevertStatement
  • SimpleComments
  • SortedImports
  • StatementBlock
  • StructDefinition
  • ThisExpression
  • TrailingComma
    • NOTE: solar error
  • TryStatement
  • TypeDefinition
  • UnitExpression
  • UsingDirective
  • VariableAssignment
  • VariableDefinition
  • WhileStatement
  • Yul
    • STYLE: pending review
  • YulStrings

Supported Configs:

  • indent_style = IndentStyle
  • multiline_func_header: MultilineFuncHeaderStyle
  • line_length: usize
  • tab_width: usize
  • bracket_spacing: bool
  • int_types: IntTypes
  • quote_style: QuoteStyle
  • number_underscore: NumberUnderscore
  • hex_underscore: HexUnderscore
  • single_line_statement_blocks: SingleLineBlockStyle
  • override_spacing: bool
  • wrap_comments: bool
  • ignore: Vec<String>
  • contract_new_lines: bool
  • sort_imports: bool
  • no_space_power: bool feat(fmt): option to remove spaces between number and exponent #4512

Copy link
Member

@DaniPopes DaniPopes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a pass over tests

*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}

/**
* @dev See {IERC721-balanceOf}.
* @dev See {IERC721-balanceOf}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm this is kinda weird, i dont think we want to touch indentation inside of comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as you know, for doc comments we must always add the * for new lines.

in the old impl, when adding it, they would add a tab, which made things inconsistent when some lines had * and in other it was added by the formatter.

personally i'd either ensure that there is always a tab (like i did) or completely get rid of tabs in commments

(10, 20, 30);
(uint256 listItem004, uint256 listItem005, uint256 listItem006) = (
10, 20, 30
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm is this right? shouldn't each the tuple element go on a new line? I don't mind it, just checking

Copy link
Contributor Author

@0xrusowsky 0xrusowsky Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we support both depending on whether the selected style is Consistent (everything breaks) or Compact (tries to isolate all items on the same line and if they don't fit everything breaks).

I can change it if we want to

@DaniPopes
Copy link
Member

I've looked at all the tests that are in the diff and LGTM, you can mark the "STYLE" in the description as completed

@0xrusowsky 0xrusowsky moved this to Ready For Review in Foundry Aug 17, 2025
@jenpaff jenpaff moved this from Ready For Review to In Progress in Foundry Aug 18, 2025
@@ -2,20 +2,20 @@
contract A {
Counter public counter;
/**
* TODO: this fuzz use too much time to execute
* function testGetFuzz(bytes[2][] memory kvs) public {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DaniPopes this is an example of a block cmnt where they would add a *\t but which is inconsistent with the user-defined * TODO

that's why i chose to add tabs everywhere

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

3 participants